namespace Warehouse

open System
open System.Data
open System.Collections.ObjectModel
open System.Linq
open System.Windows
open System.IO
open Microsoft.Win32
open System.Data.OleDb
open FSharp.Windows

type InvoiceDetailsController(repository : DataModel.Repository) = 
    inherit Controller<InvoiceDetailsEvents, InvoiceDetailsModel>()

    let mutable monsoonFilesToArchive = [||]

    override this.InitModel model = 
        model.AllClientsList <- repository.Clients |> Seq.sortBy(fun x -> x.CompanyName)
        if model.IsNewEntity 
        then 
            model.Date <- DateTime.Now
            model.LineItems <- ObservableCollection() |> ObservableCollection.deleteEntityOnRemove repository

    override this.Dispatcher = Sync << function
        | Save ok -> this.Save ok
        | BulkInput -> this.BulkInput
        | AddLineItem -> this.AddLineItem

    member this.Save ok model = 

        if model.IsNewEntity && Seq.isEmpty model.LineItems 
        then
            model |> Validation.setError <@ fun m -> m.LineItems @> "There are no line items."

        model.Validate repository

        if model.IsValid
        then 
            for li in model.LineItems do
                li.Commission <-
                    match model.CommissionOverride.AsOption() with
                    | Some value -> value
                    | None ->
                        li.UPC.Product.ClientCommission model.Client.Id

            model.Save repository
            ok()
 
    member this.BulkInput model = 
        let view = UpcQuantityPasteView()
        let controller = UpcQuantityPasteController view
        Mvc.startDialog(view, controller) 
            |> Option.iter (fun upcQtyItemsModel ->

                let groupedByUpc = upcQtyItemsModel.Items |> Seq.groupBy fst |> Seq.map (fun(upc, xs) -> upc, xs |> Seq.map snd |> Seq.sum) 
                model |> Validation.clearError <@fun m -> m.LineItems@>

                for upCode, quantity in groupedByUpc do
                    let lineItem : InvoiceLineItemModel = Model.Create()
                    match model.LineItems |> Seq.tryFind (fun li -> li.UPCode = upCode) with
                    | Some lineItem -> 
                        lineItem.Quantity <- lineItem.Quantity + quantity
                    | None -> 
                        lineItem.UPC <- repository.UpcById upCode
                        if lineItem.UPC <> null
                        then
                            lineItem.BasePrice <- lineItem.UPC.Product.ShopCost
                        else
                            lineItem.UPC <- DataModel.UPC(Code = upCode)
                            lineItem |> Validation.setError <@ fun m -> m.UPCode @> "UPC doesn't exist in database."

                        lineItem.Quantity <- quantity
                        model.LineItems.Add lineItem 
            )        

    member this.AddLineItem model = 
        let x = Model.Create()
        let mvc = UpcSearchController.Mvc(x, repository)
        if mvc.StartDialog() 
        then 
            let upcEntity = x.SelectedUPC.Entity

            if model.LineItems |> Seq.exists (fun li -> li.UPCode = upcEntity.Code) 
            then
                let message = sprintf "Line item with upc %s already exists." upcEntity.Code
                MessageBox.Error(message, "Duplicated UPC")
            else
                let li : InvoiceLineItemModel = Model.Create()
                li.UPC <- upcEntity
                let product = upcEntity.Product
                li.BasePrice <- product.ShopCost
                li.Commission <- product.ClientCommission model.Client.Id
                li.Compute()
                model.LineItems.Add li
                model.SelectedLineItem <- li

    member private this.AllocateQuantityBySku(sku : string, product : DataModel.Product, requestedQuantity : int) = 
        let rec allocator leftToAllocated (upcs : DataModel.UPC list) acc = 
            if leftToAllocated = 0 then acc
            else
                let lineItem : InvoiceLineItemModel = Model.Create()
                lineItem.UPC <- upcs.Head
                lineItem.BasePrice <- lineItem.UPC.Product.ShopCost
                let available = repository.AvailableQuantityByUPC lineItem.UPC
                if leftToAllocated > available && upcs.Tail.IsEmpty
                then
                    lineItem.Quantity <- leftToAllocated
                    lineItem :: acc
                elif available > 0 
                then
                    lineItem.Quantity <- min leftToAllocated available
                    let acc' = if lineItem.Quantity <> 0 then lineItem :: acc else acc
                    allocator (leftToAllocated - lineItem.Quantity) upcs.Tail acc'
                else
                    allocator (leftToAllocated - lineItem.Quantity) upcs.Tail acc

        if product = null
        then
            let lineItem : InvoiceLineItemModel = Model.Create()
            lineItem.UPC <- DataModel.UPC(Code = sku)
            lineItem |> Validation.setError <@ fun m -> m.UPCode @> "UPC doesn't exist in database."
            [ lineItem ]
        else
            let upcs = 
                product.UPCs
                |> List.ofSeq
                |> List.sortWith(fun x y ->
                    match x.UpcNote.Code, y.UpcNote.Code with
                    | "OP", _ | "NE", "NW" -> -1
                    | _, "OP" | "NW", "NE" -> 1
                    | code1, code2 -> if code1 = code2 then 0 else failwith "Unexpected codes combination: %s and %s" code1 code2
                )

            allocator requestedQuantity upcs []

